#include <string.h>
#include "filesys/cache.h"
#include "filesys/filesys.h"
#include "threads/malloc.h"
#include "threads/thread.h"

void cache_init ()
{
   cache_list_init ();
   lock_init (&cache_lock);
   cache_bitmap = bitmap_create (CACHE_SIZE_LIMIT);
   cache_victim_idx = 0;
   //thread_create ("cache_write_behind", PRI_DEFAULT, &cache_write_behind, NULL);
}

void cache_list_init ()
{ 
   int i = 0;

   for (; i < CACHE_SIZE_LIMIT; i++)
   {
       cache_list[i] = cache_initialize_sector ();
   }   
}

void cache_read_sector (disk_sector_t arg_sector_idx, void* arg_buf, int arg_ofs, int arg_size)
{
    if (arg_ofs < DISK_SECTOR_SIZE)
    {
       cache_read_or_write_sector (arg_sector_idx, arg_buf, arg_ofs, arg_size, true);
    }
}

void cache_write_sector (disk_sector_t arg_sector_idx, void* arg_buf, int arg_ofs, int arg_size)
{
    if (arg_ofs + arg_size <= DISK_SECTOR_SIZE)
    {
       cache_read_or_write_sector (arg_sector_idx, arg_buf, arg_ofs, arg_size, false);
    }
}

void cache_read_or_write_sector (disk_sector_t arg_sector_idx, void* arg_buf, int arg_ofs, int arg_size, bool is_read)
{
    int temp_idx = cache_find_sector (arg_sector_idx);
        
    if (temp_idx == -1)
    {
       temp_idx = cache_create_sector (arg_sector_idx);
    }
    if (temp_idx == -1)
    {
       return;
    }

    struct cache_disk_sector* temp = cache_list[temp_idx];
   
    if (is_read)
    {
       memcpy (arg_buf, temp->cache_buffer + arg_ofs, arg_size);
    }
    else
    {
       memcpy (temp->cache_buffer + arg_ofs, arg_buf, arg_size);
       temp->cache_dirty = true;
    }
    temp->cache_access_bit = true;
}

void cache_select_victim ()
{
    while (true)
    {
       if (cache_list[cache_victim_idx]->cache_access_bit)
       {
	  cache_list[cache_victim_idx]->cache_access_bit = false;
       }
       else
       {
	  cache_write_back_sector (cache_victim_idx);
	  bitmap_set (cache_bitmap, cache_victim_idx, false);
	  return;
       }
       cache_victim_idx = (cache_victim_idx + 1) % CACHE_SIZE_LIMIT;
    }
}

void cache_flush_sector ()
{
    lock_acquire (&cache_lock);
        
    int i = 0;
    for (; i < CACHE_SIZE_LIMIT; i++)
    {
	cache_write_back_sector (i);
    }

    lock_release (&cache_lock);
}

struct cache_disk_sector* cache_initialize_sector ()
{
    struct cache_disk_sector* temp = (struct cache_disk_sector*) malloc (sizeof(struct cache_disk_sector));
    temp->cache_access_bit = false;
    temp->cache_dirty = false;
    temp->cache_sec_idx = -1;	   
    temp->cache_buffer = malloc (DISK_SECTOR_SIZE);
    return temp;
}

void cache_write_back_sector (int arg_idx)
{
   if (bitmap_test (cache_bitmap, arg_idx))
   {
      if (cache_list[arg_idx]->cache_dirty)
      {
         disk_write (filesys_disk, cache_list[arg_idx]->cache_sec_idx, cache_list[arg_idx]->cache_buffer);
      }
   }
}

int cache_find_sector (disk_sector_t arg_sector_idx)
{
   lock_acquire (&cache_lock);

   int i = 0;
   for (; i < CACHE_SIZE_LIMIT; i++) 
   {
       if (cache_list[i]->cache_sec_idx == arg_sector_idx)
       {
	   lock_release (&cache_lock);                        
           return i;
       }       
   }
   lock_release (&cache_lock);

   return -1;
}

size_t cache_create_sector (disk_sector_t arg_sector_idx)
{
   lock_acquire (&cache_lock);

   size_t available_sector = bitmap_scan (cache_bitmap, 0, 1, false);

   if (available_sector == BITMAP_ERROR) 
   {
      cache_select_victim ();
      available_sector = bitmap_scan (cache_bitmap, 0, 1, false);
   }
   if (available_sector == BITMAP_ERROR)
   {
      lock_release (&cache_lock);
      return -1;
   }
   struct cache_disk_sector* temp = cache_list[available_sector];

   disk_read (filesys_disk, arg_sector_idx, temp->cache_buffer);

   temp->cache_sec_idx = arg_sector_idx;
   temp->cache_access_bit = false;
   temp->cache_dirty = false;

   bitmap_set (cache_bitmap, available_sector, true);

   if (arg_sector_idx+1 != (disk_sector_t)-1)
   {
      // read ahead
      size_t ahead_available_sector = bitmap_scan (cache_bitmap, 0, 1, false);
      if (ahead_available_sector == BITMAP_ERROR)
      {
	goto skip_ahead;
      }

      struct cache_disk_sector* ahead_temp = cache_list[ahead_available_sector];
      disk_read (filesys_disk, arg_sector_idx+1, ahead_temp->cache_buffer);
      ahead_temp->cache_sec_idx = arg_sector_idx + 1;
      ahead_temp->cache_access_bit = false;
      ahead_temp->cache_dirty = false;
      bitmap_set (cache_bitmap, ahead_available_sector, true);
   }

skip_ahead:
   lock_release (&cache_lock);

   return available_sector;
}

void cache_write_behind_sector ()
{
    lock_acquire (&cache_lock);
        
    int i = 0;
    for (; i < CACHE_SIZE_LIMIT; i++)
    {
	if (cache_list[i]->cache_dirty)
        {
           disk_write (filesys_disk, cache_list[i]->cache_sec_idx, cache_list[i]->cache_buffer);
        }
    }

    lock_release (&cache_lock);
}

void cache_write_behind ()
{
    uint64_t temp_sleep_time = 1000;

    while (1)
    {
         cache_write_behind_sector ();
         timer_sleep (temp_sleep_time);
    }
}
